package x86.imp;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import assem.Instr;
import assem.OPER;

import temp.Label;
import temp.Temp;
import tree.BINOP;
import tree.CALL;
import tree.CJUMP;
import tree.CONST;
import tree.ESEQ;
import tree.EXPSTM;
import tree.Exp;
import tree.JUMP;
import tree.LABEL;
import tree.MEM;
import tree.MOVE;
import tree.NAME;
import tree.PrintIR;
import tree.SEQ;
import tree.Stm;
import tree.TEMP;
import util.List;
import x86.Codegen;
import x86.Frame;

public class SelectionHandler 
{
	private Codegen cg;	
	private static final String space = "    ";
	
	private Temp tempMovConst;
	
	public SelectionHandler(Codegen cg)
	{
		this.cg = cg;
		this.tempMovConst = new Temp();
	}
	
	private static <E> util.List<E> append(util.List<E> list, E value)
	{
		if (list == null)
			list = new List<E>(value, null);
		else
		{
			util.List<E> l;
			
			for (l = list; l.tail != null; l = l.tail)
				;
			
			l.tail = new List<E>(value, null);
		}
		
		return list;		
	}
	
	private static String feed(String s)
	{
		return s + "\n";
	}
	
	private void emit(Instr it)
	{
		cg.emit(it);
	}
	
	private Temp matchIntern(BINOP s)
	{
		return matchIntern(s, true);
	}
	
	private Temp matchIntern(BINOP s, boolean createNewTemp)
	{
		String op, src, dst;
		Temp tsrc, tdst;
		boolean comutativo;
		
		tsrc = null;
		comutativo = false;
		
		switch(s.binop)
		{
			case BINOP.LSHIFT:
				op = "SHL ";
				break;
			case BINOP.MINUS:
				op = "SUB ";
				break;
			case BINOP.PLUS:
				op = "ADD ";
				comutativo = true;
				break;
			case BINOP.TIMES:
				op = "IMUL ";
				comutativo = true;
				break;
			case BINOP.XOR:
				op = "XOR ";
				comutativo = true;
				break;
			default:
				throw new Error("Invalid binop");
		}
		
		if (s.left instanceof CONST && comutativo)
		{
			// ajustamos o binop   a op b ==> b op a  se a operacao eh comutativa
			Exp etmp = s.left;
			s.left = s.right;
			s.right = etmp;
		}
		
		if (s.right instanceof CONST)
		{
			src = String.valueOf(((CONST)s.right).value);
		}
		else
		{
			tsrc = matchIntern(s.right);
			src = "`s1";
		}
		
		tdst = matchIntern(s.left);
	
		if (createNewTemp)
		{
			Temp newTemp = new Temp();
			matchIntern(new MOVE(new TEMP(newTemp), new TEMP(tdst)));
			tdst = newTemp;
		}
		
		// se nao precisamos criar novo registrador, entao o destino é um dos operandos
		// e estamos diante de uma soma
		// se for incremento, alteramos operacao
		if (!createNewTemp && s.binop == BINOP.PLUS && s.right instanceof CONST && ((CONST)s.right).value == 1)
		{
			op = "INC `d0";
		}
		else
		{
			// caso geral op dst, src
			op += "`d0," + src;
		}
				
		util.List<Temp> uses = new util.List<Temp>(tdst, null);
		
		if (tsrc != null)
			uses.tail = new util.List<Temp>(tsrc, null);
			
		emit(new OPER(op, 
				new util.List<Temp>(tdst, null), uses));
		
		return tdst;
	}
	
	private Temp matchIntern(CALL s)
	{		
		util.List<Temp> uses = null;
		util.List<Temp> calldefs = Frame.calldefs;
		
		StringBuilder str = new StringBuilder();
		int nargs = 0;
		int tempPos = 0;
		String label;
		
		if (s.func instanceof NAME)
		{
			Label lb = ((NAME)s.func).label;
			label = lb.toString();
		}
		else
		{
			Temp func = matchIntern(s.func);
			label = "`s0";
			
			uses = new util.List<Temp>(func, null);
			tempPos++;
		}	
		
		for (util.List<Exp> args = s.args; args != null; args = args.tail)
		{
			Exp arg = args.head;
			nargs++;
			
			if (arg instanceof NAME)
			{
				str.insert(0, feed(space + "PUSH " + ((NAME)arg).label.toString()));
				continue;
			}
			
			Temp t = matchIntern(arg);
			
			if (uses == null)
				uses = new util.List<Temp>(t, null);
			else
				uses.append(t);
			
			String push = space + "PUSH `s";
			// argumentos inseridos em ordem inversa
			str.insert(0, feed(push + String.valueOf(tempPos)));
			
			tempPos++;
		}
		
		str.delete(0, space.length());
		
		str.append(space + "CALL " + label);
		
		emit(new OPER(str.toString(), calldefs, uses));
		
		// arruma pilha
		Stm restFrame = new MOVE(new TEMP(Frame.esp), 
				new BINOP(BINOP.PLUS, new TEMP(Frame.esp), new CONST(nargs * 4)));
		matchIntern(restFrame);
		
		return Frame.eax;
	}	
	
	private void matchIntern(CJUMP s)
	{
		Temp left, right;
		
		left = matchIntern(s.left);
		right = matchIntern(s.right);

		String op = "CMP `s0,`s1";
		String cnd = space;
		
		switch(s.op)
		{
			case CJUMP.EQ:
				cnd += "JE";
				break;
			case CJUMP.NE:
				cnd += "JNE";
				break;
			case CJUMP.GE:
				cnd += "JGE";
				break;
			case CJUMP.LT:
				cnd += "JL";
				break;
			default:
				throw new Error("Invalid cmp");
		}
		
		cnd += " " + s.ifTrue.toString();
		
		op = feed(op) + feed(cnd);
		
		emit(new OPER(op, null, new util.List<Temp>(left, new util.List<Temp>(right, null)), 
				new util.List<Label>(s.ifTrue, new util.List<Label>(s.ifFalse, null))));
	}
	
	private void matchIntern(JUMP s)
	{		
		String target;
		
		if (!(s.exp instanceof NAME))
			throw new Error("Invalid JUMP target");
		
		target = ((NAME)s.exp).label.toString();
		
		emit(new OPER(feed("JMP " + target), null, null, ((NAME)s.exp).label)); 
	}
	
	private Temp matchIntern(MEM s)
	{
		Temp t = new Temp();
		Temp tsrc = null;
		List<Temp> tsrcList;
		
		String src;

		if (s.exp instanceof CONST)
		{
			src = String.valueOf(((CONST)s.exp).value);
			tsrcList = null;
		}
		else
		{
			tsrc = matchIntern(s.exp);
			src = "`s0";
			
			tsrcList = new List<Temp>(tsrc, null);
		}
		
		
		String instr = "MOV `d0,[ds:" + src + "]";
		
		//emit(new assem.MOVE(feed(instr), t, tsrc));
		emit(new assem.OPER(instr, new List<Temp>(t, null), tsrcList));
		
		return t;
	}
	
	private void matchIntern(MOVE s)
	{		
		String dest, src;
		Temp tdest;
		util.List<Temp> srcList = null;
		boolean movRegReg;
		
		dest = "";
		movRegReg = false;
		tdest = null;		
		
		// move para memoria
		if (s.dst instanceof MEM)
		{			
			srcList = append(srcList, matchIntern(((MEM)s.dst).exp));
						
			if (s.src instanceof CONST)
			{
				src = String.valueOf(((CONST)s.src).value);
				dest = "DWORD ";
			}
			else
			{
				srcList = append(srcList, matchIntern(s.src));
				src = "`s1";
			}
			
			dest += "[ds:`s0]";
		}
		else if (s.dst instanceof TEMP)
		{		
			// caso especial move (r, BINOP(r, x)) pode virar BINOP (r, x) somente
			// pois binop guarda resultado no registrador da esquerda
			if (s.src instanceof BINOP)
			{
				BINOP bp = (BINOP)s.src;
				Temp tempDest = ((TEMP)s.dst).temp;
				
				if (bp.left instanceof TEMP && tempDest == ((TEMP)bp.left).temp ||
						bp.right instanceof TEMP && tempDest == ((TEMP)bp.right).temp)
				{
					// se o registrador da direita for o destino, temos que consertar o binop
					if (bp.right instanceof TEMP && tempDest == ((TEMP)bp.right).temp)
					{
						Exp etmp = bp.left;
						bp.left = bp.right;
						bp.right = etmp;
					}
					
					// temos o caso especial
					matchIntern(bp, false);
					return;
				}
			}
			
			if (s.src instanceof MEM)
			{
				MEM m = (MEM)s.src;

				if (m.exp instanceof TEMP)
				{
					srcList = append(srcList, ((TEMP)m.exp).temp);
					src = "[ds:`s0]";
				}
				else
				{
					srcList = append(srcList, matchIntern(m));
					src = "`s0";
					movRegReg = true;
				}
				
			}
			else if (s.src instanceof CONST)
			{
				src = String.valueOf(((CONST)s.src).value);
			}
			else
			{
				srcList = append(srcList, matchIntern(s.src));
				src = "`s0";
				movRegReg = true;
			}
			
			tdest = ((TEMP)s.dst).temp; 
			dest = "`d0";
		}
		else
			throw new Error("Invalid MOV dest");
		
		String instr = "MOV " + dest + "," + src;
		
		Instr it;
				
		//if (tdest != null && srcList != null && srcList.size() == 1)
		if(movRegReg)
		{
			it = new assem.MOVE(instr, tdest, srcList.head);
		}
		else
		{
			util.List<Temp> dstList;
			
			if (tdest == null)
				dstList = null;
			else
				dstList = new util.List<Temp>(tdest, null);
			
			it = new assem.OPER(instr, dstList, srcList);
		}
		
		emit(it);
	}
	
	private Temp matchIntern(ESEQ s)
	{				
		matchIntern(s.stm);
		
		return matchIntern(s.exp);
	}
	
	private void matchIntern(EXPSTM s)
	{		
		matchIntern(s.exp);
	}
	
	private void matchIntern(SEQ s)
	{		
		matchIntern(s.left);
		matchIntern(s.right);
	}
	
	private Temp matchIntern(CONST s)
	{	
		Temp t = new Temp();
		MOVE mv = new MOVE(new TEMP(t), s);
		matchIntern(mv);
		
//		// vamos zerar registrador e somar valor desejado
//		
//		TEMP tt = new TEMP(t);
//		
//		// zeramos registrador
//		// retorno deve ser igual a t
//		matchIntern(new BINOP(BINOP.XOR, tt, tt));
//		
//		// se queremos outro valor além de 0, temos que somar o valor
//		if (s.value != 0)
//		{
//			matchIntern(new BINOP(BINOP.PLUS, tt, s));
//		}
//		
		return t;
	}
		
	private void matchIntern(LABEL s)
	{		
		emit(new assem.LABEL(s.label.toString() + ":", s.label));
	}
	
	private Temp matchIntern(TEMP s)
	{		
		return s.temp;
	}
	
	private Temp matchIntern(Exp s)
	{
		Temp t = null;
		
		if (s instanceof BINOP)
			t = matchIntern((BINOP)s);
		else if (s instanceof CALL)
			t = matchIntern((CALL)s);
		else if (s instanceof CONST)
			t = matchIntern((CONST)s);
		else if (s instanceof ESEQ)
			t = matchIntern((ESEQ)s);
		else if (s instanceof MEM)
			t = matchIntern((MEM)s);
		else if (s instanceof NAME)
			throw new Error("Invalid call NAME");
		else if (s instanceof TEMP)
			t = matchIntern((TEMP)s);
		else
			throw new Error("Invalid match");
		
		return t;		
	}
	
	private void matchIntern(Stm s)
	{
		if (s instanceof JUMP)
			matchIntern((JUMP)s);
		else if (s instanceof LABEL)
			matchIntern((LABEL)s);
		else if (s instanceof MOVE)
			 matchIntern((MOVE)s);
		else if (s instanceof SEQ)
			 matchIntern((SEQ)s);
		else if (s instanceof CJUMP)
			 matchIntern((CJUMP)s);
		else if (s instanceof EXPSTM)
			 matchIntern((EXPSTM)s);
		else
			throw new Error("Invalid match");
	}
	
	public void doMatch(Stm s)
	{
//        tree.PrintIR pt = new PrintIR(System.out);
//    	pt.printStatement(s);
//    	System.out.println("------------");
		
		matchIntern(s);
	}
}
